#include <GL/glew.h>
#include "SFML/Graphics.hpp"
#include "LME_Shader.hpp"
#include "Image.hpp"
#include <map>
#include <iostream>

#ifndef __MATERIAL_HPP__
#define __MATERIAL_HPP__

using namespace std;

class Material {
	protected:
		/** pointer to shader program used in this Material */
    	LME_ShaderProgram* m_shader;
	public:
		/**  link this material to be used as the current one */
		virtual void use() = 0;

		/** unlinked material */
		virtual void unuse() = 0;

		/** function to retrieve the shader programm linked to this material
		 *	return pointer to the internal shader program */
		virtual LME_ShaderProgram* getShaderProgram() { return 0;};
};


class ShaderTexturedMaterial : public Material {
	protected:
		/** arrays of texture identifier */
    	GLuint* m_texture;

		/** number of texture used in this Material */
		int textureNumber; 
  public:
	/** Material constuctor, initializing shader pointer to NULL and creating 
	  * an array of length 1 for storing the texture */
    ShaderTexturedMaterial() { m_shader = 0; m_texture = new GLuint[1]; textureNumber = 1;};

	/** function for defining shader program pointer */
    void setShader(LME_ShaderProgram* shader) { m_shader = shader;};

	/** retrieve the number of texture level define for this Material */
	int getTextureNumber() { return textureNumber;};

	/** function for setting texture indexed by index, if
	  * index is superior to (textureNumber - 1) array texture 
	  * is extended */
    void setTexture(GLuint tex, int index = 0) { 
		if (index > textureNumber - 1) {
		GLuint* tmp = new GLuint[index+1];
		for (int i = 0; i < textureNumber; i++) tmp[i] = m_texture[i];
		delete m_texture;
		textureNumber++;
		m_texture = tmp;
		};
		m_texture[index] = tex;
	};

	/** function for setting this Material as the current one, 
	  * that will be use for all rendering coming after this
	  * call and before other Material use or call to unuse() */
    virtual void use() { 
		for (int i = 0; i < textureNumber; i++) { 
			GLint textureFlag;
			switch(i) {
				case 0: 
					textureFlag = GL_TEXTURE0; break;
				case 1:
					textureFlag = GL_TEXTURE1; break;
				case 2:
					textureFlag = GL_TEXTURE2; break;
				case 3:
					textureFlag = GL_TEXTURE3; break;
				case 4:
					textureFlag = GL_TEXTURE4; break;
				default:
					std::cout << "Error : this level of texture is not managed by LME." << endl;
					textureFlag = GL_TEXTURE0; break;
			};
			glActiveTexture(textureFlag);
			glBindTexture(GL_TEXTURE_2D,m_texture[i]);
		};
		m_shader->use(); 
	};

	/** function for coming back to openGL state without
	  * this Material use */
    virtual void unuse() { 
		m_shader->unuse(); 
		glActiveTexture(GL_TEXTURE0);
		glBindTexture(GL_TEXTURE_2D,0);
	}

	/** function for retrieving pointer to shader program */
    virtual LME_ShaderProgram* getShaderProgram() { return m_shader; };
};

class ShaderMaterial : public Material {
	protected:
		/** pointer to shader program used by this Material */
		LME_ShaderProgram* m_shader;
	public:
		/** Material constructor (initializing shader program pointer to NULL) */
		ShaderMaterial() { m_shader = 0; };

		/** function for setting shader program pointer */
		void setShader(LME_ShaderProgram* shader) { m_shader = shader;};
		virtual void use() { m_shader->use(); };
		virtual void unuse() { m_shader->unuse();};
		virtual LME_ShaderProgram* getShaderProgram() { return m_shader; };
};



class MaterialGestionner {
  protected:
    static map<string, GLuint> m_textureMap;
	static map<string, GenericImage*> m_imageMap;
    static map<string, LME_VertexShader*> m_vertexShaderMap;
    static map<string, LME_FragmentShader*> m_fragmentShaderMap;
    static map<string, LME_ShaderProgram*> m_programShaderMap;
    static map<string, Material*> m_materialMap;
  public:
    static Material* addTexturedMaterial(string name, string texture, string vertexShader, string fragShader);
	static Material* addMaterial(string name, string vertexShader, string fragShader);
    static LME_VertexShader* getVertexShader(string name);
    static LME_FragmentShader* getFragmentShader(string name);
    static LME_ShaderProgram* getShaderProgram(string name, string vertexShaderName, string fragmentShaderName);
    static GLuint getTexture(string);
	static void loadImage(string filename);
	static GenericImage* getImage(string filename);
	/** function to force the material gestionner to load a texture
	 * if the texture already exists, the engine will overwrite it
	 * keeping its id
	 * param[in] filename   file name of the wanted texture image file
	 */
	static GLuint forceLoadingTexture(string filename);
};

#endif
